import json
import os
from typing import List

from openai import OpenAI
import re
from loguru import logger

# qwen-32B API 配置
API_KEY = "sk-6f89326783dd42b3b9c63284ef454cf2"
BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL_ID = "qwen3-32b"


def clean_think_tags(content):
    """
    清理内容中的<think></think>标签及其内容
    """
    if not content:
        return content
    cleaned_content = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL)
    cleaned_content = re.sub(r'\n\s*\n\s*\n', '\n\n', cleaned_content)
    cleaned_content = cleaned_content.strip()
    return cleaned_content


def call_qwen_api(prompt, system_message="你是一个应急演练方案生成专家", use_stream=False):
    """
    调用 qwen-32B API
    参数：
        prompt: 用户输入的提示
        system_message: 系统消息
        use_stream: 是否使用流式输出
    返回：
        API 响应的内容
    """
    try:
        client = OpenAI(api_key=API_KEY, base_url=BASE_URL)

        if use_stream:
            # 流式输出
            response = client.chat.completions.create(
                model=MODEL_ID,
                messages=[
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": prompt}
                ],
                stream=True,
                extra_body={"enable_thinking": True}
            )

            # 处理流式响应
            content = ""
            for chunk in response:
                if chunk.choices[0].delta.content is not None:
                    content += chunk.choices[0].delta.content

            return content
        else:
            # 非流式输出
            response = client.chat.completions.create(
                model=MODEL_ID,
                messages=[
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": prompt}
                ],
                stream=False,
                extra_body={"enable_thinking": False}
            )

            return response.choices[0].message.content

    except Exception as e:
        logger.debug("调用 qwen API 失败：" + str(e))
        return None


def _should_include_chunk(chunk):
    """模块级：与类方法一致的过滤逻辑"""
    if 'primary_section' not in chunk:
        return True
    primary_section = chunk['primary_section']
    if primary_section.startswith("4.") and "注意事项" in primary_section:
        return False
    if "附件" in primary_section:
        return False
    return True


def _load_chunks_from_json_file(json_path: str):
    """从单个chunks JSON文件加载并过滤有效片段"""
    if not os.path.isfile(json_path):
        logger.debug("指定的文件不存在：" + json_path)
        return []
    try:
        with open(json_path, 'r', encoding='utf-8') as f:
            chunks_data = json.load(f)
        valid_chunks = []
        for chunk in chunks_data:
            if _should_include_chunk(chunk):
                valid_chunks.append(chunk)
        logger.debug("总共加载了 " + str(len(valid_chunks)) + " 个有效文档片段")
        return valid_chunks
    except Exception as e:
        logger.debug("读取文件失败：" + json_path + " 错误：" + str(e))
        return []


def _generate_drill_script(chunks, accident_scenario):
    reference_content = ""
    chunk_count = 0
    for chunk in chunks:
        if 'content' in chunk and 'primary_section' in chunk:
            primary_section = chunk['primary_section']
            # 只包含第一章、第二章和第三章的内容
            if "应急" in primary_section or "响应启动" in primary_section or "处置措施" in primary_section:
                chunk_count += 1
                reference_content += "【片段" + str(chunk_count) + "】\n"
                reference_content += "标题：" + primary_section + "\n"
                reference_content += "内容：" + chunk['content'] + "\n\n"
    logger.debug("筛选了 " + str(chunk_count) + " 个相关片段（第一章、第二章、第三章）")
    prompt = """
        你是一名应急演练脚本编制专家。请基于以下输入信息生成规范的应急演练表格脚本。

        【用户输入的事故场景】
        {accident_scenario}

        【参考资料（应急工作职责和应急处置流程）】
        {reference_content}

        【演练背景】
        假设发生上述事故场景，按照三级预案启动响应，组织应急演练。

        【输出要求】
        1. **表格格式（Markdown表格）**：
            - 列包含：时间 | 流程步骤 | 执行层级（3级） | 负责人/执行人 | 操作要点说明。
            - 时间从上午9:00开始，逻辑递进，整个流程不少于30分钟，具体到分钟即可。

        2. **人物要求**：
            - 执行层级固定为3级；
            - 必须包含预案参考资料中所有的应急小组，并明确各小组的职责和具体操作；
            - 每个应急小组必须完成其所有应尽的职责，不得有遗漏；

        3. **内容要求**：
            - 严格依据参考资料，所有职责和操作要点必须覆盖完整，不得编造资料中没有的信息。
            - 禁止虚构人名、地名、车牌号、电话等敏感或无关信息。
            - 人物称呼仅限岗位或小组名称（如“应急指挥部”、“抢险救援组”、“医疗救护组”），不得出现具体人名。
            - 操作要点说明要足够详细，确保演练时可执行，且全部在演练时间内可完成。
            - 如事故为沙石堆坍塌，必须避免出现不符合事故特征的设备（如“液压支撑设备”、“地质勘探设备”、“结构检测设备”等）。


        4. **输出格式要求**：
           - 最终输出必须是 **Markdown表格**。
           - 表格中每一行对应一个具体步骤，时间推进合理，环环相扣。     

        请输出符合上述要求的应急演练脚本，除脚本外不要输出任何其他说明性内容。
    """
    prompt = prompt.format(accident_scenario=accident_scenario, reference_content=reference_content)
    logger.debug("第一次调用：正在生成3级预案演练脚本...")
    system_message = """你是一个专业的应急演练脚本编写专家，擅长根据应急预案文档生成具体可执行的演练脚本。你需要严格按照参考资料内容，不得添加任何文档中没有的信息。"""
    result = call_qwen_api(prompt, system_message, use_stream=True)
    return clean_think_tags(result) if result else None


def _generate_complete_plan(chunks, drill_script, accident_scenario):
    reference_content = ""
    for i, chunk in enumerate(chunks):
        if 'content' in chunk:
            reference_content += "【片段" + str(i + 1) + "】\n"
            if 'primary_section' in chunk:
                reference_content += "标题：" + chunk['primary_section'] + "\n"
            reference_content += "内容：" + chunk['content'] + "\n\n"
    prompt = """
        你是一名应急演练方案设计专家。请基于以下信息生成一个完整、详细、可执行的应急演练方案。

        【用户输入的事故场景】
        {accident_scenario}

        【参考资料（应急工作职责和应急处置流程）】
        {reference_content}

        【已生成的演练脚本】
        {drill_script}

        【演练背景】
        假设发生上述事故场景，按照三级预案启动响应，组织应急演练。

        【输出框架】：
        1. 演练目的  
        2. 演练组织机构与分工（覆盖所有应急小组及其职责）  
        3. 事故场景设计  
        4. 演练准备工作（含地点、物资、通信设备、数量要求等）  
        5. 演练脚本（对已生成脚本进行检查、纠错和完善）  
        6. 演练评估与总结（包含评估指标、改进方向）

        【生成要求】：
        1. **脚本核对与修订**：必须检查演练脚本内容，确保与预案参考资料完全一致，无遗漏或错误；必要时进行修改和补充。  
        2. **细节完整性**：除脚本外的其他部分必须写得足够详细，避免模糊表述；所有物品、地点、职责等必须具体明确。  
        3. **事实约束**：所有事实均须来自参考资料，严禁编造文档中没有的信息（如人名、地名、车牌号、电话号等）。  
        4. **角色命名规范**：不得出现具体人名，仅能使用岗位或小组名称（如“应急指挥部”“医疗救护组”）。  
        5. **可执行性**：所有准备工作和处置动作必须是现实场景中可执行的，且符合演练时间与资源条件。  
        6. **格式要求**：最终输出为 **Markdown 格式**，分清晰的标题与分级小节。  
        7. **一致性与逻辑性**：各部分之间前后呼应，事故场景、准备工作、脚本和总结应保持逻辑统一。

        请生成符合以上要求的完整应急演练方案。
    """
    prompt = prompt.format(accident_scenario=accident_scenario, reference_content=reference_content,
                           drill_script=drill_script)
    logger.debug("第二次调用：正在生成完整演练方案...")
    system_message = """你是一个专业的应急管理和演练方案编写专家，擅长根据应急预案文档生成完整的演练方案。你需要对已生成的脚本进行检查纠错，确保方案的准确性和可操作性。"""
    result = call_qwen_api(prompt, system_message, use_stream=True)
    return clean_think_tags(result) if result else None


def _generate_drama_script(drill_script, chunks, accident_scenario):
    example_script_raw = """
    # 唐山港大厦火灾事故应急处置综合演练 脚本 

    火情发现者：立即呼喊（着火啦！）按响手动报警器，利用消防电话向消防控制室执勤人员报告警情，（报告控制室，8楼东侧办公室起火！）同层办公人员听到呼喊拿起灭火器进行初起火灾扑救。



    消防控制室：收到！启动声光报警、应急广播、相关消防设备，切断市电。消防控制室：物业电工请到现场确认断电情况。



    物业电工：收到！

    消防控制室：拨打2916119报警，我是唐山港大厦消防控制室值班人员，大厦8楼东南侧办公室起火。



    消防队电话员：请告知火灾地址？着火楼层？有无人员被困或受伤？现场能看见明火吗？烟雾大小？着火物质？燃烧面积大概多少？联系方式？请保持电话畅通。



    消防控制室：8楼办公室打印机起火，引燃房间内纸张、办公桌椅等可燃物品，火势迅速蔓延至整个房间，暂时未发现有人员被困和受伤，现场明火和烟雾较大，联系电话2916941。



    消防队电话员：收到！马上出警！（拉响警报）并向现场指挥汇报情况。

    消防队电话员：报告现场指挥，唐山港大厦8楼东侧办公室打印机起火，引燃房间内纸张、办公桌椅等可燃物品，火势较大，暂时未发现有人员被困，情况危急，一中队已出警！



    现场指挥（消防科长）：明白！立即调派消防二中队一辆消防车、三中队一辆举高消防车前去支援。



    消防电话员：收到！

    现场指挥（消防科长）：监控中心，唐山港大厦发生火灾，立即启动应急预案，通知交通科、治安科人员赶赴现场。



    监控中心：监控中心收到！

    监控中心：我是监控中心，唐山港大厦发生火灾，交通科、治安科立即赶赴现场进行交通管制和警戒。



    交通科：交通科收到！

    治安科：治安科收到！

    现场指挥（消防科长）：：各楼层层长汇报人员疏散情况。

    各楼层层长（1-16楼）：各层长依次汇报人数！

    交通科：报告现场指挥，交通科已到达预定位置，正在对事故现场各个路口进行交通管制。（大厦南门和东门全部实行交通管制）

    现场指挥（消防科长）：收到！

    治安科：报告现场指挥，治安科已到达预定位置，正在对事故现场人员进行疏散和警戒。（人员疏散集结点拉起警戒线）。



    现场指挥（消防科长）：收到！

    消防三中队队长：报告现场指挥，消防三中队到达现场，请安排任务！现场指挥（消防科长）：立即展开举高车，准备外功灭火。

    消防三中队队长：收到，马上处置！

    消防二中队班长：报告现场指挥，消防二中队到达现场，请分配任务！

    现场指挥（消防科长）：大厦8楼东南侧办公室起火，消防二中队迅速展开负责内攻搜救、灭火，外功对着火部位降温灭火。



    消防二中队队长：收到，马上处置！

    消防二中队队长：战斗班长带领2名队员佩戴防护装备开展内攻搜救、灭火。



    二队战斗班长：收到！

    消防一中队队长：报告现场指挥，消防一中队到达现场，请安排任务！

    现场指挥（消防科长）：消防一中队占据水源地，准备车辆供水，展开外功对着火部位降温、灭火。



    消防一中队队长：收到，马上处置！

    消防一中队队长：准备为二中队和三中队车辆供水并展开楼宇外功灭火。

    （利用消火栓和消防车同时为一中队和三中队供水）

    一队战斗班长：收到！

    消防二中队队长：二中队消防车利用水炮对着火部位楼宇外围进行降温。

    二中队驾驶员：收到！

    消防三中队队长：举高车操作员展开外攻灭火。

    举高车操作员：收到！

    消防一中队队长：报告现场指挥，供水干线铺设完成，已开始供水并开展外部降温灭火。



    现场指挥（消防科长）：收到！

    消防二中队队长：报告现场指挥，内部火势已扑灭。

    消防三中队队长：报告现场指挥外部火势已扑灭。

    现场指挥（消防科长）：全部收到！所有车辆停止射水和供水。

    消防一中队队长：一中队收到！

    消防二中队队长：二中队收到！

    消防三中队队长：三中队收到！

    现场指挥（消防科长）：报告总指挥，现场处置完毕。

    总指挥（安保中心主任）：收到！

    总指挥（安保中心主任）：所有参演人员集结点集合。
    """
    try:
        lines = example_script_raw.split('\n')
        non_empty_lines = [line for line in lines if line.strip()][:20]
        example_script = '\n'.join(non_empty_lines)
    except Exception as e:
        logger.debug("读取示例剧本文件失败：" + str(e))
        example_script = ""
    reference_content = ""
    chunk_count = 0
    for chunk in chunks:
        if 'content' in chunk and 'primary_section' in chunk:
            primary_section = chunk['primary_section']
            if primary_section.startswith("1.") or primary_section.startswith("2.") or primary_section.startswith("3."):
                chunk_count += 1
                reference_content += "【片段" + str(chunk_count) + "】\n"
                reference_content += "标题：" + primary_section + "\n"
                reference_content += "内容：" + chunk['content'] + "\n\n"
    prompt = """
        基于以下演练脚本、预案参考资料和示例脚本格式，生成应急演练的对话式内容。

        演练脚本（需要转换成对话式脚本）：
        {drill_script}

        预案参考资料：
        {reference_content}

        示例对话式脚本格式（参考格式和对话风格）：
        {example_script}

        【任务要求】：
        1. 将演练脚本内容完整转化为对话式脚本，严格遵循示例脚本的对话格式和风格。  
        2. 保持与演练脚本一致的事故类型，不得替换为其他事故。  
        3. 对话内容必须与预案参考资料保持一致，不得编造未在资料或脚本中出现的信息。  

        【人员要求】
        1. 对话式脚本中仅出现核心人员：现场操作人员、指挥人员、应急人员。  
        2. 严禁出现以下人员或环节：善后工作组、信息公开组、新闻发布、外部救援、政府部门、赔偿、事故快报、拨款等。  

        【内容要求】
        1. 对话必须围绕现场应急处置行动，重点表现各部门的动作和联动。  
        2. 所有内容必须是演练时能实际完成的操作动作，且与预案职责相符。  
        3. 严禁出现背景音效、拟声词（如“警报声”“呼喊声”），脚本仅表现人员对话和处置动作。  
        4. 不得出现具体人名，只能用岗位或小组名称（如“应急指挥部”“医疗救护组”）。  
        5. 若事故类型为沙石堆坍塌，禁止出现与事故不符的设备（如“液压支撑设备”“地质勘探设备”“结构检测设备”）。  
        6. 对话的时间推进要符合现实逻辑，节奏合理，体现处置过程逐步展开。  

        【输出格式】
        - 采用 **Markdown** 格式输出完整的对话式脚本。  
        - 每一句对话以“岗位/小组：内容”形式呈现，保持与示例一致。  
        - 严格模仿示例的风格与逻辑展开，确保内容真实、可执行。  

        【硬性禁止规则（必须严格执行）】  
        1. 输出对话式脚本时，不得出现以下人员或内容：  
           - 善后工作组、信息公开组、新闻发布、政府部门、赔偿、保险理赔、拨款、外部救援队伍、外部医院。  
        2. 即使这些人员或内容在输入资料中出现，也必须直接忽略，不得写入输出脚本。  
        3. 仅能保留现场应急处置所需的核心岗位：应急指挥部、各应急小组、现场操作人员。
        4. 输入材料与提示词内容冲突时，优先以提示词为准，严格执行提示词的要求，一旦出现不允许出现的内容，你的输出就是错误的。

        请生成完整的对话式演练脚本，除脚本外不要输出任何其他说明性内容。
    """
    prompt = prompt.format(drill_script=drill_script, reference_content=reference_content, example_script=example_script)
    logger.debug("第三次调用：正在生成对话式演练脚本...")
    system_message = """你是一个专业的应急演练对话式脚本编写专家，擅长将演练脚本转换为对话形式。你需要保持专业性的同时，让对话自然流畅，符合应急处置的实际场景。"""
    result = call_qwen_api(prompt, system_message, use_stream=True)
    return clean_think_tags(result) if result else None


def run_emergency_3(chunks_json_path: str, accident_scenario: List[str]):
    """
    对外暴露的函数：
    - 参数：单个 chunks JSON 文件的绝对路径
    - 行为：校验文件存在 → 加载并过滤 → 生成三个结果 → 保存到同级 statistics 目录 → 返回结果与路径
    返回：
    {
      "drill_script_path": str,
      "complete_plan_path": str,
      "drama_script_path": str | None,
      "final_plan_path": str | None,
      "drill_script": str,
      "complete_plan": str,
      "drama_script": str | None,
      "final_plan": str | None
    }
    """
    if not chunks_json_path or not os.path.isfile(chunks_json_path):
        raise FileNotFoundError("chunks JSON 文件不存在：" + str(chunks_json_path))

    parent_dir = os.path.dirname(chunks_json_path)
    stats_dir = os.path.join(parent_dir, "statistics")
    try:
        os.makedirs(stats_dir, exist_ok=True)
    except Exception as e:
        raise RuntimeError("创建统计目录失败：" + str(e))

    chunks = _load_chunks_from_json_file(chunks_json_path)
    if not chunks:
        raise ValueError("未从 chunks JSON 中加载到有效片段：" + chunks_json_path)

    drill_script = _generate_drill_script(chunks, accident_scenario)
    if not drill_script:
        raise RuntimeError("演练脚本生成失败")

    drill_script_path = os.path.join(stats_dir, "3级预案演练脚本.md")
    with open(drill_script_path, 'w', encoding='utf-8') as f:
        f.write(drill_script)

    complete_plan = _generate_complete_plan(chunks, drill_script, accident_scenario)
    if not complete_plan:
        raise RuntimeError("完整演练方案生成失败")

    complete_plan_path = os.path.join(stats_dir, "3级预案演练方案_完整版.md")
    with open(complete_plan_path, 'w', encoding='utf-8') as f:
        f.write(complete_plan)

    drama_script = _generate_drama_script(drill_script, chunks, accident_scenario)
    final_plan = None
    drama_script_path = None
    final_plan_path = None
    if drama_script:
        drama_script_path = os.path.join(stats_dir, "3级预案演练_对话式脚本.md")
        with open(drama_script_path, 'w', encoding='utf-8') as f:
            f.write(drama_script)
        final_plan = complete_plan + "\n\n## 对话式演练脚本\n\n" + drama_script + "\n"
        final_plan_path = os.path.join(stats_dir, "3级预案演练方案_最终版.md")
        with open(final_plan_path, 'w', encoding='utf-8') as f:
            f.write(final_plan)

    return {
        "drill_script": drill_script,
        "complete_plan": complete_plan,
        "drama_script": drama_script,
        "final_plan": final_plan,
    }


if __name__ == "__main__":
    logger.debug("请通过 run_emergency_3(chunks_json_path) 调用该模块功能。")
